/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief FBO colorbuffer tests.
 *//*--------------------------------------------------------------------*/

#include "es3fFboColorbufferTests.hpp"
#include "es3fFboTestCase.hpp"
#include "es3fFboTestUtil.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRGBA.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"
#include "sglrContextUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles3
{
namespace Functional
{

using std::string;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::UVec4;
using tcu::TestLog;
using namespace FboTestUtil;

const tcu::RGBA MIN_THRESHOLD(12, 12, 12, 12);

template <int Size>
static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
{
	tcu::Vector<float, Size> res;
	for (int ndx = 0; ndx < Size; ndx++)
		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
	return res;
}

static tcu::Vec4 generateRandomColor (de::Random& random)
{
	tcu::Vec4 retVal;

	for (int i = 0; i < 3; ++i)
		retVal[i] = random.getFloat();
	retVal[3] = 1.0f;

	return retVal;
}

class FboColorbufferCase : public FboTestCase
{
public:
	FboColorbufferCase (Context& context, const char* name, const char* desc, const deUint32 format)
		: FboTestCase			(context, name, desc)
		, m_format				(format)
	{
	}

	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
	{
		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_format), MIN_THRESHOLD));

		m_testCtx.getLog() << TestLog::Message << "Comparing images, threshold: " << threshold << TestLog::EndMessage;

		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
	}

protected:
	const deUint32	m_format;
};

class FboColorClearCase : public FboColorbufferCase
{
public:
	FboColorClearCase (Context& context, const char* name, const char* desc, deUint32 format, int width, int height)
		: FboColorbufferCase	(context, name, desc, format)
		, m_width				(width)
		, m_height				(height)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		tcu::TextureFormat			fboFormat	= glu::mapGLInternalFormat(m_format);
		tcu::TextureChannelClass	fmtClass	= tcu::getTextureChannelClass(fboFormat.type);
		tcu::TextureFormatInfo		fmtInfo		= tcu::getTextureFormatInfo(fboFormat);
		de::Random					rnd			(17);
		const int					numClears	= 16;
		deUint32					fbo			= 0;
		deUint32					rbo			= 0;

		glGenFramebuffers(1, &fbo);
		glGenRenderbuffers(1, &rbo);

		glBindRenderbuffer(GL_RENDERBUFFER, rbo);
		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_width, m_height);
		checkError();

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
		checkError();
		checkFramebufferStatus(GL_FRAMEBUFFER);

		glViewport(0, 0, m_width, m_height);

		// Initialize to transparent black.
		switch (fmtClass)
		{
			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
			case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
				glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
				break;

			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
				glClearBufferuiv(GL_COLOR, 0, UVec4(0).getPtr());
				break;

			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
				glClearBufferiv(GL_COLOR, 0, IVec4(0).getPtr());
				break;

			default:
				DE_ASSERT(DE_FALSE);
		}

		// Do random scissored clears.
		glEnable(GL_SCISSOR_TEST);
		for (int ndx = 0; ndx < numClears; ndx++)
		{
			int		x		= rnd.getInt(0, m_width		- 1);
			int		y		= rnd.getInt(0, m_height	- 1);
			int		w		= rnd.getInt(1, m_width		- x);
			int		h		= rnd.getInt(1, m_height	- y);
			Vec4	color	= randomVector<4>(rnd, fmtInfo.valueMin, fmtInfo.valueMax);

			glScissor(x, y, w, h);

			switch (fmtClass)
			{
				case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
				case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
				case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
					glClearBufferfv(GL_COLOR, 0, color.getPtr());
					break;

				case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
					glClearBufferuiv(GL_COLOR, 0, color.cast<deUint32>().getPtr());
					break;

				case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
					glClearBufferiv(GL_COLOR, 0, color.cast<int>().getPtr());
					break;

				default:
					DE_ASSERT(DE_FALSE);
			}
		}

		// Read results from renderbuffer.
		readPixels(dst, 0, 0, m_width, m_height, fboFormat, fmtInfo.lookupScale, fmtInfo.lookupBias);
		checkError();
	}

private:
	const int			m_width;
	const int			m_height;
};

class FboColorMultiTex2DCase : public FboColorbufferCase
{
public:
	FboColorMultiTex2DCase (Context& context, const char* name, const char* description, deUint32 tex0Fmt, const IVec2& tex0Size, deUint32 tex1Fmt, const IVec2& tex1Size)
		: FboColorbufferCase	(context, name, description, tex0Fmt)
		, m_tex0Fmt				(tex0Fmt)
		, m_tex1Fmt				(tex1Fmt)
		, m_tex0Size			(tex0Size)
		, m_tex1Size			(tex1Size)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_tex0Fmt);
		checkFormatSupport(m_tex1Fmt);
	}

	void render (tcu::Surface& dst)
	{
		tcu::TextureFormat		texFmt0			= glu::mapGLInternalFormat(m_tex0Fmt);
		tcu::TextureFormat		texFmt1			= glu::mapGLInternalFormat(m_tex1Fmt);
		tcu::TextureFormatInfo	fmtInfo0		= tcu::getTextureFormatInfo(texFmt0);
		tcu::TextureFormatInfo	fmtInfo1		= tcu::getTextureFormatInfo(texFmt1);

		Texture2DShader			texToFbo0Shader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt0), fmtInfo0.valueMax-fmtInfo0.valueMin, fmtInfo0.valueMin);
		Texture2DShader			texToFbo1Shader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt1), fmtInfo1.valueMax-fmtInfo1.valueMin, fmtInfo1.valueMin);
		Texture2DShader			multiTexShader	(DataTypes() << glu::getSampler2DType(texFmt0) << glu::getSampler2DType(texFmt1), glu::TYPE_FLOAT_VEC4);

		deUint32				texToFbo0ShaderID = getCurrentContext()->createProgram(&texToFbo0Shader);
		deUint32				texToFbo1ShaderID = getCurrentContext()->createProgram(&texToFbo1Shader);
		deUint32				multiTexShaderID  = getCurrentContext()->createProgram(&multiTexShader);

		// Setup shaders
		multiTexShader.setTexScaleBias(0, fmtInfo0.lookupScale * 0.5f, fmtInfo0.lookupBias * 0.5f);
		multiTexShader.setTexScaleBias(1, fmtInfo1.lookupScale * 0.5f, fmtInfo1.lookupBias * 0.5f);
		texToFbo0Shader.setUniforms(*getCurrentContext(), texToFbo0ShaderID);
		texToFbo1Shader.setUniforms(*getCurrentContext(), texToFbo1ShaderID);
		multiTexShader.setUniforms (*getCurrentContext(), multiTexShaderID);

		// Framebuffers.
		deUint32				fbo0, fbo1;
		deUint32				tex0, tex1;

		for (int ndx = 0; ndx < 2; ndx++)
		{
			glu::TransferFormat		transferFmt		= glu::getTransferFormat(ndx ? texFmt1 : texFmt0);
			deUint32				format			= ndx ? m_tex1Fmt : m_tex0Fmt;
			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(format);
			const IVec2&			size			= ndx ? m_tex1Size : m_tex0Size;
			deUint32&				fbo				= ndx ? fbo1 : fbo0;
			deUint32&				tex				= ndx ? tex1 : tex0;

			glGenFramebuffers(1, &fbo);
			glGenTextures(1, &tex);

			glBindTexture(GL_TEXTURE_2D, tex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexImage2D(GL_TEXTURE_2D, 0, format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);

			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
			checkError();
			checkFramebufferStatus(GL_FRAMEBUFFER);
		}

		// Render textures to both framebuffers.
		for (int ndx = 0; ndx < 2; ndx++)
		{
			const deUint32		format		= GL_RGBA;
			const deUint32		dataType	= GL_UNSIGNED_BYTE;
			const int			texW		= 128;
			const int			texH		= 128;
			deUint32			tmpTex		= 0;
			deUint32			fbo			= ndx ? fbo1 : fbo0;
			const IVec2&		viewport	= ndx ? m_tex1Size : m_tex0Size;
			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

			if (ndx == 0)
				tcu::fillWithComponentGradients(data.getAccess(), Vec4(0.0f), Vec4(1.0f));
			else
				tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));

			glGenTextures(1, &tmpTex);
			glBindTexture(GL_TEXTURE_2D, tmpTex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glViewport(0, 0, viewport.x(), viewport.y());
			sglr::drawQuad(*getCurrentContext(), ndx ? texToFbo1ShaderID : texToFbo0ShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		}

		// Render to framebuffer.
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glViewport(0, 0, getWidth(), getHeight());
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, tex0);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, tex1);
		sglr::drawQuad(*getCurrentContext(), multiTexShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		readPixels(dst, 0, 0, getWidth(), getHeight());
	}

private:
	deUint32		m_tex0Fmt;
	deUint32		m_tex1Fmt;
	IVec2			m_tex0Size;
	IVec2			m_tex1Size;
};

class FboColorTexCubeCase : public FboColorbufferCase
{
public:
	FboColorTexCubeCase			(Context& context, const char* name, const char* description, deUint32 texFmt, const IVec2& texSize)
		: FboColorbufferCase	(context, name, description, texFmt)
		, m_texSize				(texSize)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		static const deUint32 cubeGLFaces[] =
		{
			GL_TEXTURE_CUBE_MAP_POSITIVE_X,
			GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
			GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
			GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
			GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
			GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
		};

		static const tcu::CubeFace cubeTexFaces[] =
		{
			tcu::CUBEFACE_POSITIVE_X,
			tcu::CUBEFACE_POSITIVE_Y,
			tcu::CUBEFACE_POSITIVE_Z,
			tcu::CUBEFACE_NEGATIVE_X,
			tcu::CUBEFACE_NEGATIVE_Y,
			tcu::CUBEFACE_NEGATIVE_Z
		};

		de::Random				rnd					(deStringHash(getName()) ^ 0x9eef603d);
		tcu::TextureFormat		texFmt				= glu::mapGLInternalFormat(m_format);
		tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);

		Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
		TextureCubeShader		cubeTexShader		(glu::getSamplerCubeType(texFmt), glu::TYPE_FLOAT_VEC4);

		deUint32				texToFboShaderID	= getCurrentContext()->createProgram(&texToFboShader);
		deUint32				cubeTexShaderID		= getCurrentContext()->createProgram(&cubeTexShader);

		// Setup shaders
		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
		cubeTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);

		// Framebuffers.
		std::vector<deUint32>	fbos;
		deUint32				tex;

		{
			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
			const IVec2&			size			= m_texSize;


			glGenTextures(1, &tex);

			glBindTexture(GL_TEXTURE_CUBE_MAP,		tex);
			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);

			// Generate an image and FBO for each cube face
			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cubeGLFaces); ndx++)
				glTexImage2D(cubeGLFaces[ndx], 0, m_format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);
			checkError();

			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cubeGLFaces); ndx++)
			{
				deUint32			layerFbo;

				glGenFramebuffers(1, &layerFbo);
				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubeGLFaces[ndx], tex, 0);
				checkError();
				checkFramebufferStatus(GL_FRAMEBUFFER);

				fbos.push_back(layerFbo);
			}
		}

		// Render test images to random cube faces
		std::vector<int> order;

		for (size_t n = 0; n < fbos.size(); n++)
			order.push_back((int)n);
		rnd.shuffle(order.begin(), order.end());

		DE_ASSERT(order.size() >= 4);
		for (int ndx = 0; ndx < 4; ndx++)
		{
			const int			face		= order[ndx];
			const deUint32		format		= GL_RGBA;
			const deUint32		dataType	= GL_UNSIGNED_BYTE;
			const int			texW		= 128;
			const int			texH		= 128;
			deUint32			tmpTex		= 0;
			const deUint32		fbo			= fbos[face];
			const IVec2&		viewport	= m_texSize;
			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));

			glGenTextures(1, &tmpTex);
			glBindTexture(GL_TEXTURE_2D, tmpTex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glViewport(0, 0, viewport.x(), viewport.y());
			sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
			checkError();

			// Render to framebuffer
			{
				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);

				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glViewport(0, 0, getWidth(), getHeight());

				glActiveTexture(GL_TEXTURE0);
				glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

				cubeTexShader.setFace(cubeTexFaces[face]);
				cubeTexShader.setUniforms(*getCurrentContext(), cubeTexShaderID);

				sglr::drawQuad(*getCurrentContext(), cubeTexShaderID, p0, p1);
				checkError();
			}
		}

		readPixels(dst, 0, 0, getWidth(), getHeight());
	}

private:
	IVec2			m_texSize;
};

class FboColorTex2DArrayCase : public FboColorbufferCase
{
public:
	FboColorTex2DArrayCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec3& texSize)
		: FboColorbufferCase	(context, name, description, texFmt)
		, m_texSize				(texSize)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		de::Random				rnd					(deStringHash(getName()) ^ 0xed607a89);
		tcu::TextureFormat		texFmt				= glu::mapGLInternalFormat(m_format);
		tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);

		Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
		Texture2DArrayShader	arrayTexShader		(glu::getSampler2DArrayType(texFmt), glu::TYPE_FLOAT_VEC4);

		deUint32				texToFboShaderID	= getCurrentContext()->createProgram(&texToFboShader);
		deUint32				arrayTexShaderID	= getCurrentContext()->createProgram(&arrayTexShader);

		// Setup textures
		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
		arrayTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);

		// Framebuffers.
		std::vector<deUint32>	fbos;
		deUint32				tex;

		{
			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
			bool					isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
			const IVec3&			size			= m_texSize;


			glGenTextures(1, &tex);

			glBindTexture(GL_TEXTURE_2D_ARRAY,		tex);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);

			// Generate an FBO for each layer
			for (int ndx = 0; ndx < m_texSize.z(); ndx++)
			{
				deUint32			layerFbo;

				glGenFramebuffers(1, &layerFbo);
				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
				checkError();
				checkFramebufferStatus(GL_FRAMEBUFFER);

				fbos.push_back(layerFbo);
			}
		}

		// Render test images to random texture layers
		std::vector<int>		order;

		for (size_t n = 0; n < fbos.size(); n++)
			order.push_back((int)n);
		rnd.shuffle(order.begin(), order.end());

		for (size_t ndx = 0; ndx < order.size(); ndx++)
		{
			const int			layer		= order[ndx];
			const deUint32		format		= GL_RGBA;
			const deUint32		dataType	= GL_UNSIGNED_BYTE;
			const int			texW		= 128;
			const int			texH		= 128;
			deUint32			tmpTex		= 0;
			const deUint32		fbo			= fbos[layer];
			const IVec3&		viewport	= m_texSize;
			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));

			glGenTextures(1, &tmpTex);
			glBindTexture(GL_TEXTURE_2D, tmpTex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glViewport(0, 0, viewport.x(), viewport.y());
			sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
			checkError();

			// Render to framebuffer
			{
				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);

				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glViewport(0, 0, getWidth(), getHeight());

				glActiveTexture(GL_TEXTURE0);
				glBindTexture(GL_TEXTURE_2D_ARRAY, tex);

				arrayTexShader.setLayer(layer);
				arrayTexShader.setUniforms(*getCurrentContext(), arrayTexShaderID);

				sglr::drawQuad(*getCurrentContext(), arrayTexShaderID, p0, p1);
				checkError();
			}
		}

		readPixels(dst, 0, 0, getWidth(), getHeight());
	}

private:
	IVec3			m_texSize;
};

class FboColorTex3DCase : public FboColorbufferCase
{
public:
	FboColorTex3DCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec3& texSize)
		: FboColorbufferCase	(context, name, description, texFmt)
		, m_texSize				(texSize)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		de::Random				rnd				(deStringHash(getName()) ^ 0x74d947b2);
		tcu::TextureFormat		texFmt			= glu::mapGLInternalFormat(m_format);
		tcu::TextureFormatInfo	fmtInfo			= tcu::getTextureFormatInfo(texFmt);

		Texture2DShader			texToFboShader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
		Texture3DShader			tdTexShader		(glu::getSampler3DType(texFmt), glu::TYPE_FLOAT_VEC4);

		deUint32				texToFboShaderID= getCurrentContext()->createProgram(&texToFboShader);
		deUint32				tdTexShaderID	= getCurrentContext()->createProgram(&tdTexShader);

		// Setup shaders
		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
		tdTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);

		// Framebuffers.
		std::vector<deUint32>	fbos;
		deUint32				tex;

		{
			glu::TransferFormat		transferFmt		= glu::getTransferFormat(texFmt);
			const IVec3&			size			= m_texSize;

			glGenTextures(1, &tex);

			glBindTexture(GL_TEXTURE_3D,		tex);
			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
			glTexParameteri(GL_TEXTURE_3D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
			glTexImage3D(GL_TEXTURE_3D, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);

			// Generate an FBO for each layer
			for (int ndx = 0; ndx < m_texSize.z(); ndx++)
			{
				deUint32			layerFbo;

				glGenFramebuffers(1, &layerFbo);
				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
				checkError();
				checkFramebufferStatus(GL_FRAMEBUFFER);

				fbos.push_back(layerFbo);
			}
		}

		// Render test images to random texture layers
		std::vector<int> order;

		for (size_t n = 0; n < fbos.size(); n++)
			order.push_back((int)n);
		rnd.shuffle(order.begin(), order.end());

		for (size_t ndx = 0; ndx < order.size(); ndx++)
		{
			const int			layer		= order[ndx];
			const deUint32		format		= GL_RGBA;
			const deUint32		dataType	= GL_UNSIGNED_BYTE;
			const int			texW		= 128;
			const int			texH		= 128;
			deUint32			tmpTex		= 0;
			const deUint32		fbo			= fbos[layer];
			const IVec3&		viewport	= m_texSize;
			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

			tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));

			glGenTextures(1, &tmpTex);
			glBindTexture(GL_TEXTURE_2D, tmpTex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glViewport(0, 0, viewport.x(), viewport.y());
			sglr::drawQuad(*getCurrentContext() , texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
			checkError();

			// Render to framebuffer
			{
				const Vec3		p0	= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
				const Vec3		p1	= p0 + Vec3(1.0f, 1.0f, 0.0f);

				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glViewport(0, 0, getWidth(), getHeight());

				glActiveTexture(GL_TEXTURE0);
				glBindTexture(GL_TEXTURE_3D, tex);

				tdTexShader.setDepth(float(layer) / float(m_texSize.z()-1));
				tdTexShader.setUniforms(*getCurrentContext(), tdTexShaderID);

				sglr::drawQuad(*getCurrentContext(), tdTexShaderID, p0, p1);
				checkError();
			}
		}

		readPixels(dst, 0, 0, getWidth(), getHeight());
	}

private:
	IVec3			m_texSize;
};

class FboBlendCase : public FboColorbufferCase
{
public:
	FboBlendCase (Context& context, const char* name, const char* desc, deUint32 format, IVec2 size, deUint32 funcRGB, deUint32 funcAlpha, deUint32 srcRGB, deUint32 dstRGB, deUint32 srcAlpha, deUint32 dstAlpha)
		: FboColorbufferCase	(context, name, desc, format)
		, m_size				(size)
		, m_funcRGB				(funcRGB)
		, m_funcAlpha			(funcAlpha)
		, m_srcRGB				(srcRGB)
		, m_dstRGB				(dstRGB)
		, m_srcAlpha			(srcAlpha)
		, m_dstAlpha			(dstAlpha)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		// \note Assumes floating-point or fixed-point format.
		tcu::TextureFormat			fboFmt			= glu::mapGLInternalFormat(m_format);
		Texture2DShader				texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
		GradientShader				gradShader		(glu::TYPE_FLOAT_VEC4);
		deUint32					texShaderID		= getCurrentContext()->createProgram(&texShader);
		deUint32					gradShaderID	= getCurrentContext()->createProgram(&gradShader);
		deUint32					fbo				= 0;
		deUint32					rbo				= 0;

		// Setup shaders
		texShader.setUniforms (*getCurrentContext(), texShaderID);
		gradShader.setGradient(*getCurrentContext(), gradShaderID, tcu::Vec4(0.0f), tcu::Vec4(1.0f));

		glGenFramebuffers(1, &fbo);
		glGenRenderbuffers(1, &rbo);

		glBindRenderbuffer(GL_RENDERBUFFER, rbo);
		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.x(), m_size.y());
		checkError();

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
		checkError();
		checkFramebufferStatus(GL_FRAMEBUFFER);

		glViewport(0, 0, m_size.x(), m_size.y());

		// Fill framebuffer with grid pattern.
		{
			const deUint32		format		= GL_RGBA;
			const deUint32		dataType	= GL_UNSIGNED_BYTE;
			const int			texW		= 128;
			const int			texH		= 128;
			deUint32			gridTex		= 0;
			tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

			tcu::fillWithGrid(data.getAccess(), 8, Vec4(0.2f, 0.7f, 0.1f, 1.0f), Vec4(0.7f, 0.1f, 0.5f, 0.8f));

			glGenTextures(1, &gridTex);
			glBindTexture(GL_TEXTURE_2D, gridTex);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

			sglr::drawQuad(*getCurrentContext(), texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		}

		// Setup blend.
		glEnable(GL_BLEND);
		glBlendEquationSeparate(m_funcRGB, m_funcAlpha);
		glBlendFuncSeparate(m_srcRGB, m_dstRGB, m_srcAlpha, m_dstAlpha);

		// Render gradient with blend.
		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		readPixels(dst, 0, 0, m_size.x(), m_size.y(), fboFmt, Vec4(1.0f), Vec4(0.0f));
	}

private:
	IVec2		m_size;
	deUint32	m_funcRGB;
	deUint32	m_funcAlpha;
	deUint32	m_srcRGB;
	deUint32	m_dstRGB;
	deUint32	m_srcAlpha;
	deUint32	m_dstAlpha;
};

FboColorTests::FboColorTests (Context& context)
	: TestCaseGroup(context, "color", "Colorbuffer tests")
{
}

FboColorTests::~FboColorTests (void)
{
}

void FboColorTests::init (void)
{
	static const deUint32 colorFormats[] =
	{
		// RGBA formats
		GL_RGBA32I,
		GL_RGBA32UI,
		GL_RGBA16I,
		GL_RGBA16UI,
		GL_RGBA8,
		GL_RGBA8I,
		GL_RGBA8UI,
		GL_SRGB8_ALPHA8,
		GL_RGB10_A2,
		GL_RGB10_A2UI,
		GL_RGBA4,
		GL_RGB5_A1,

		// RGB formats
		GL_RGB8,
		GL_RGB565,

		// RG formats
		GL_RG32I,
		GL_RG32UI,
		GL_RG16I,
		GL_RG16UI,
		GL_RG8,
		GL_RG8I,
		GL_RG8UI,

		// R formats
		GL_R32I,
		GL_R32UI,
		GL_R16I,
		GL_R16UI,
		GL_R8,
		GL_R8I,
		GL_R8UI,

		// GL_EXT_color_buffer_float
		GL_RGBA32F,
		GL_RGBA16F,
		GL_R11F_G11F_B10F,
		GL_RG32F,
		GL_RG16F,
		GL_R32F,
		GL_R16F,

		// GL_EXT_color_buffer_half_float
		GL_RGB16F
	};

	// .clear
	{
		tcu::TestCaseGroup* clearGroup = new tcu::TestCaseGroup(m_testCtx, "clear", "Color clears");
		addChild(clearGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
			clearGroup->addChild(new FboColorClearCase(m_context, getFormatName(colorFormats[ndx]), "", colorFormats[ndx], 129, 117));
	}

	// .tex2d
	{
		tcu::TestCaseGroup* tex2DGroup = new tcu::TestCaseGroup(m_testCtx, "tex2d", "Texture 2D tests");
		addChild(tex2DGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
			tex2DGroup->addChild(new FboColorMultiTex2DCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
															colorFormats[fmtNdx], IVec2(129, 117),
															colorFormats[fmtNdx], IVec2(99, 128)));
	}

	// .texcube
	{
		tcu::TestCaseGroup* texCubeGroup = new tcu::TestCaseGroup(m_testCtx, "texcube", "Texture cube map tests");
		addChild(texCubeGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
			texCubeGroup->addChild(new FboColorTexCubeCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
														   colorFormats[fmtNdx], IVec2(128, 128)));
	}

	// .tex2darray
	{
		tcu::TestCaseGroup* tex2DArrayGroup = new tcu::TestCaseGroup(m_testCtx, "tex2darray", "Texture 2D array tests");
		addChild(tex2DArrayGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
			tex2DArrayGroup->addChild(new FboColorTex2DArrayCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
																 colorFormats[fmtNdx], IVec3(128, 128, 5)));
	}

	// .tex3d
	{
		tcu::TestCaseGroup* tex3DGroup = new tcu::TestCaseGroup(m_testCtx, "tex3d", "Texture 3D tests");
		addChild(tex3DGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
			tex3DGroup->addChild(new FboColorTex3DCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
													   colorFormats[fmtNdx], IVec3(128, 128, 5)));
	}

	// .blend
	{
		tcu::TestCaseGroup* blendGroup = new tcu::TestCaseGroup(m_testCtx, "blend", "Blending tests");
		addChild(blendGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
		{
			deUint32					format		= colorFormats[fmtNdx];
			tcu::TextureFormat			texFmt		= glu::mapGLInternalFormat(format);
			tcu::TextureChannelClass	fmtClass	= tcu::getTextureChannelClass(texFmt.type);
			string						fmtName		= getFormatName(format);

			if (texFmt.type	== tcu::TextureFormat::FLOAT				||
				fmtClass	== tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER	||
				fmtClass	== tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
				continue; // Blending is not supported.

			blendGroup->addChild(new FboBlendCase(m_context, (fmtName + "_src_over").c_str(), "", format, IVec2(127, 111), GL_FUNC_ADD, GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE));
		}
	}
}

} // Functional
} // gles3
} // deqp
